/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.ant.internal; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Handler; import java.util.logging.Level; import org.apache.commons.io.IOUtils; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.RulesetsFactoryUtils; import net.sourceforge.pmd.ant.Formatter; import net.sourceforge.pmd.ant.PMDTask; import net.sourceforge.pmd.ant.SourceLanguage; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.AbstractRenderer; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.IOUtil; import net.sourceforge.pmd.util.StringUtil; import net.sourceforge.pmd.util.datasource.DataSource; import net.sourceforge.pmd.util.datasource.FileDataSource; import net.sourceforge.pmd.util.log.AntLogHandler; import net.sourceforge.pmd.util.log.ScopedLogHandlersManager; public class PMDTaskImpl { private Path classpath; private Path auxClasspath; private final List<Formatter> formatters = new ArrayList<>(); private final List<FileSet> filesets = new ArrayList<>(); private final PMDConfiguration configuration = new PMDConfiguration(); private boolean failOnError; private boolean failOnRuleViolation; private int maxRuleViolations = 0; private String failuresPropertyName; private Project project; public PMDTaskImpl(PMDTask task) { configuration.setReportShortNames(task.isShortFilenames()); configuration.setSuppressMarker(task.getSuppressMarker()); this.failOnError = task.isFailOnError(); this.failOnRuleViolation = task.isFailOnRuleViolation(); this.maxRuleViolations = task.getMaxRuleViolations(); if (this.maxRuleViolations > 0) { this.failOnRuleViolation = true; } configuration.setRuleSets(task.getRulesetFiles()); configuration.setRuleSetFactoryCompatibilityEnabled(!task.isNoRuleSetCompatibility()); if (task.getEncoding() != null) { configuration.setSourceEncoding(task.getEncoding()); } configuration.setThreads(task.getThreads()); this.failuresPropertyName = task.getFailuresPropertyName(); configuration.setMinimumPriority(RulePriority.valueOf(task.getMinimumPriority())); configuration.setAnalysisCacheLocation(task.getCacheLocation()); SourceLanguage version = task.getSourceLanguage(); if (version != null) { LanguageVersion languageVersion = LanguageRegistry .findLanguageVersionByTerseName(version.getName() + " " + version.getVersion()); if (languageVersion == null) { throw new BuildException("The following language is not supported:" + version + "."); } configuration.setDefaultLanguageVersion(languageVersion); } classpath = task.getClasspath(); auxClasspath = task.getAuxClasspath(); filesets.addAll(task.getFilesets()); formatters.addAll(task.getFormatters()); project = task.getProject(); } private void doTask() { setupClassLoader(); // Setup RuleSetFactory and validate RuleSets RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration); try { // This is just used to validate and display rules. Each thread will create its own ruleset String ruleSets = configuration.getRuleSets(); if (StringUtil.isNotEmpty(ruleSets)) { // Substitute env variables/properties configuration.setRuleSets(project.replaceProperties(ruleSets)); } RuleSets rules = ruleSetFactory.createRuleSets(configuration.getRuleSets()); logRulesUsed(rules); } catch (RuleSetNotFoundException e) { throw new BuildException(e.getMessage(), e); } if (configuration.getSuppressMarker() != null) { project.log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE); } // Start the Formatters for (Formatter formatter : formatters) { project.log("Sending a report to " + formatter, Project.MSG_VERBOSE); formatter.start(project.getBaseDir().toString()); } // log("Setting Language Version " + languageVersion.getShortName(), // Project.MSG_VERBOSE); // TODO Do we really need all this in a loop over each FileSet? Seems // like a lot of redundancy RuleContext ctx = new RuleContext(); Report errorReport = new Report(); final AtomicInteger reportSize = new AtomicInteger(); final String separator = System.getProperty("file.separator"); for (FileSet fs : filesets) { List<DataSource> files = new LinkedList<>(); DirectoryScanner ds = fs.getDirectoryScanner(project); String[] srcFiles = ds.getIncludedFiles(); for (String srcFile : srcFiles) { File file = new File(ds.getBasedir() + separator + srcFile); files.add(new FileDataSource(file)); } final String inputPaths = ds.getBasedir().getPath(); configuration.setInputPaths(inputPaths); Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") { @Override public void start() { // Nothing to do } @Override public void startFileAnalysis(DataSource dataSource) { project.log("Processing file " + dataSource.getNiceFileName(false, inputPaths), Project.MSG_VERBOSE); } @Override public void renderFileReport(Report r) { int size = r.size(); if (size > 0) { reportSize.addAndGet(size); } } @Override public void end() { // Nothing to do } @Override public String defaultFileExtension() { return null; } // not relevant }; List<Renderer> renderers = new ArrayList<>(formatters.size() + 1); renderers.add(logRenderer); for (Formatter formatter : formatters) { renderers.add(formatter.getRenderer()); } try { PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers); } catch (RuntimeException pmde) { handleError(ctx, errorReport, pmde); } } int problemCount = reportSize.get(); project.log(problemCount + " problems found", Project.MSG_VERBOSE); for (Formatter formatter : formatters) { formatter.end(errorReport); } if (failuresPropertyName != null && problemCount > 0) { project.setProperty(failuresPropertyName, String.valueOf(problemCount)); project.log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE); } if (failOnRuleViolation && problemCount > maxRuleViolations) { throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code"); } } private void handleError(RuleContext ctx, Report errorReport, RuntimeException pmde) { pmde.printStackTrace(); project.log(pmde.toString(), Project.MSG_VERBOSE); Throwable cause = pmde.getCause(); if (cause != null) { StringWriter strWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(strWriter); cause.printStackTrace(printWriter); project.log(strWriter.toString(), Project.MSG_VERBOSE); IOUtils.closeQuietly(printWriter); if (StringUtil.isNotEmpty(cause.getMessage())) { project.log(cause.getMessage(), Project.MSG_VERBOSE); } } if (failOnError) { throw new BuildException(pmde); } errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename())); } private void setupClassLoader() { if (classpath == null) { classpath = new Path(project); } /* * 'basedir' is added to the path to make sure that relative paths such * as "<ruleset>resources/custom_ruleset.xml</ruleset>" still work when * ant is invoked from a different directory using "-f" */ classpath.add(new Path(null, project.getBaseDir().toString())); project.log("Using the AntClassLoader: " + classpath, Project.MSG_VERBOSE); // must be true, otherwise you'll get ClassCastExceptions as classes // are loaded twice // and exist in multiple class loaders boolean parentFirst = true; configuration.setClassLoader( new AntClassLoader(Thread.currentThread().getContextClassLoader(), project, classpath, parentFirst)); try { if (auxClasspath != null) { project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE); configuration.prependClasspath(auxClasspath.toString()); } } catch (IOException ioe) { throw new BuildException(ioe.getMessage(), ioe); } } public void execute() throws BuildException { final Handler antLogHandler = new AntLogHandler(project); final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler); try { doTask(); } finally { logManager.close(); IOUtil.tryCloseClassLoader(configuration.getClassLoader()); } } private void logRulesUsed(RuleSets rules) { project.log("Using these rulesets: " + configuration.getRuleSets(), Project.MSG_VERBOSE); RuleSet[] ruleSets = rules.getAllRuleSets(); for (RuleSet ruleSet : ruleSets) { for (Rule rule : ruleSet.getRules()) { project.log("Using rule " + rule.getName(), Project.MSG_VERBOSE); } } } }